#!/usr/bin/env bash

set -e

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source $SCRIPT_DIR/common.sh

SCRIPT_PATH=
PREFIX=
DOWNLOAD_CACHE=work
DOWNLOAD_ONLY=
OPENRESTY_VER=
OPENSSL_VER=0
BORINGSSL_VER=0
KONG_OPENSSL_VER=0
SSL_PROVIDER=openssl
LUAROCKS_VER=
PCRE_VER=
FORCE=0
OPENRESTY_PATCHES=$OPENRESTY_PATCHES
KONG_NGINX_MODULE=master
RESTY_LMDB=master
RESTY_WEBSOCKET=${RESTY_WEBSOCKET:-0}
RESTY_EVENTS=main
ATC_ROUTER=${ATC_ROUTER:-0}
DEBUG=0
NPROC=
OS=
DIST=
DIST_VER=
NGINX_EXTRA_MODULES=()
KONG_DISTRIBUTION_PATH=${KONG_DISTRIBUTION_PATH:-/distribution}

PARAMS=""

main() {
  OS=$(uname -s)
  NPROC=$(n_proc)
  SCRIPT_PATH=$(dirname "$(canon_path $0)")
  OPENRESTY_PATCHES_DIR=$(canon_path "$SCRIPT_PATH/../openresty-patches")

  while (( "$#" )); do
    case "$1" in
      -p|--prefix)
        PREFIX=$2
        shift 2
        ;;
      -j|--jobs)
        NPROC=$2
        shift 2
        ;;
      --pcre)
        PCRE_VER=$2
        shift 2
        ;;
      --pcre_sha)
        PCRE_SHA=$2
        shift 2
        ;;
      --openresty)
        OPENRESTY_VER=$2
        shift 2
        ;;
      --openresty_sha)
        OPENRESTY_SHA=$2
        shift 2
        ;;
      --openssl)
        OPENSSL_VER=$2
        shift 2
        ;;
      --kong-openssl)
        KONG_OPENSSL_VER=$2
        shift 2
        ;;
      --openssl_sha)
        OPENSSL_SHA=$2
        shift 2
        ;;
      --boringssl)
        BORINGSSL_VER="$2"
        shift 2
        ;;
      --ssl-provider)
        SSL_PROVIDER="$2"
        shift 2
        ;;
      --luarocks)
        LUAROCKS_VER=$2
        shift 2
        ;;
      --luarocks_sha)
        LUAROCKS_SHA=$2
        shift 2
        ;;
      --no-openresty-patches)
        OPENRESTY_PATCHES=0
        shift 1
        ;;
      --kong-nginx-module)
        KONG_NGINX_MODULE=$2
        shift 2
        ;;
      --no-kong-nginx-module)
        KONG_NGINX_MODULE=0
        shift 1
        ;;
      --resty-lmdb)
        RESTY_LMDB=$2
        shift 2
        ;;
      --no-resty-lmdb)
        RESTY_LMDB=0
        shift 1
        ;;
      --resty-websocket)
        RESTY_WEBSOCKET=$2
        shift 1
        ;;
      --resty-events)
        RESTY_EVENTS=$2
        shift 2
        ;;
      --atc-router)
        ATC_ROUTER=$2
        shift 2
        ;;
      -f|--force)
        FORCE=1
        shift 1
        ;;
      --debug)
        DEBUG=1
        shift 1
        ;;
      --work)
        DOWNLOAD_CACHE=$2
        shift 2
        ;;
      --add-module)
        NGINX_EXTRA_MODULES+=("--add-module=$2")
        shift 2
        ;;
      --donwload-extract-only)
        DOWNLOAD_ONLY=1
        shift 1
        ;;
      -h|--help)
        show_usage
        exit 0
        ;;
      --) # end argument parsing
        shift
        break
        ;;
      -*|--*=) # unsupported flags
        echo "Error: Unsupported flag $1" >&2
        exit 1
        ;;
      *) # preserve positional arguments
        PARAMS="$PARAMS $1"
        shift
        ;;
    esac
  done
  # set positional arguments in their proper place
  eval set -- "$PARAMS"

  if [ -z "$OPENRESTY_PATCHES" ]; then
    OPENRESTY_PATCHES=master
  fi

  if [ -z "$PREFIX" ]; then
    show_usage
    fatal "prefix can not be empty"
  fi

  PREFIX=`canon_path $PREFIX`
  DOWNLOAD_CACHE=`canon_path $DOWNLOAD_CACHE`

  if [ -z "$OPENRESTY_VER" ]; then
    show_usage
    fatal "OpenResty version can not be empty"
  fi

  if [ -z "$OPENSSL_VER" -a -z "$BORINGSSL_VER" ]; then
    show_usage
    fatal "OpenSSL and BoringSSL version can not both be empty"
  fi

  if [ "$OPENSSL_VER" = 0 -a "$SSL_PROVIDER" = "openssl" ]; then
    if [ "$KONG_OPENSSL_VER" == 0 ]; then
      show_usage
      fatal "OpenSSL version not specified"
    fi
  fi

  if [ "$BORINGSSL_VER" = 0 -a "$SSL_PROVIDER" = "boringssl" ]; then
    show_usage
    fatal "BoringSSL version not specified"
  fi

  # retrieve DIST info of DIST-specific patches

  if [ -f /etc/os-release ]; then
    . /etc/os-release
    DIST=$NAME
    DIST_VER=$VERSION_ID

  elif type lsb_release >/dev/null 2>&1; then
    DIST=$(lsb_release -si)
    DIST_VER=$(lsb_release -sr)
  fi

  if [ $FORCE == 1 ]; then
    rm -rf $PREFIX $DOWNLOAD_CACHE
  fi

  NGINX_CORE_VER=$(parse_nginx_core_version $OPENRESTY_VER)

  mkdir -p $DOWNLOAD_CACHE $PREFIX
  OPENRESTY_DOWNLOAD=$DOWNLOAD_CACHE/openresty-$OPENRESTY_VER
  OPENRESTY_DESTDIR=${OPENRESTY_DESTDIR:-/}
  OPENRESTY_PREFIX=${OPENRESTY_PREFIX:-$PREFIX/openresty}
  OPENRESTY_INSTALL=$(canon_path $OPENRESTY_DESTDIR/$OPENRESTY_PREFIX)

  if [ "$OPENSSL_VER" != 0 -a "$SSL_PROVIDER" = "openssl" ]; then
    OPENSSL_DOWNLOAD=$DOWNLOAD_CACHE/openssl-$OPENSSL_VER
    OPENSSL_DESTDIR=${OPENSSL_DESTDIR:-/}
    OPENSSL_PREFIX=${OPENSSL_PREFIX:-$PREFIX/openssl}
    OPENSSL_INSTALL=$(canon_path $OPENSSL_DESTDIR/$OPENSSL_PREFIX)

    if version_lt $OPENSSL_VER 1.1; then
      # unconditionally disable module in older core since they are never tested
      KONG_NGINX_MODULE=0
    fi
  fi

  if [ "$BORINGSSL_VER" != 0 -a "$SSL_PROVIDER" = "boringssl" ]; then
    BORINGSSL_DOWNLOAD=$DOWNLOAD_CACHE/boringssl-$BORINGSSL_VER
    OPENSSL_DESTDIR=${OPENSSL_DESTDIR:-/}
    OPENSSL_PREFIX=${OPENSSL_PREFIX:-$PREFIX/.openssl}
    OPENSSL_INSTALL=$(canon_path $OPENSSL_DESTDIR/$OPENSSL_PREFIX/)
  fi

  if version_lt $NGINX_CORE_VER 1.13.6; then
    # unconditionally disable module in older core since they are never tested
    KONG_NGINX_MODULE=0
  fi

  notice "Downloading the components now..."

  pushd $DOWNLOAD_CACHE
    if [ "$KONG_OPENSSL_VER" != 0 -a "$SSL_PROVIDER" = "openssl" ]; then
      arch=$(uname -m)

      package_architecture=x86_64
      if [ "$(arch)" == "aarch64" ]; then
        package_architecture=aarch64
      fi
      with_backoff curl --fail -sSLo openssl.tar.gz https://github.com/Kong/kong-openssl/releases/download/$KONG_OPENSSL_VER/$package_architecture-$OSTYPE.tar.gz
      tar -C /tmp/build -xvf openssl.tar.gz
    elif [ "$OPENSSL_VER" != 0 -a "$SSL_PROVIDER" = "openssl" ]; then
      # OpenSSL

      if [[ ! -f $OPENSSL_INSTALL/bin/openssl && ! -d $OPENSSL_DOWNLOAD ]]; then
        warn "OpenSSL source not found, downloading..."
        set +e
        with_backoff curl --fail -sSLO https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz
        if [[ $? != 0 ]]; then
          with_backoff curl --fail -sSLO https://www.openssl.org/source/old/${OPENSSL_VER//[a-z]/}/openssl-$OPENSSL_VER.tar.gz
          [[ $? != 0 ]] && err "Could not download OpenSSL"
        fi
        set -e

        if [ ! -z ${OPENSSL_SHA+x} ]; then
          echo "$OPENSSL_SHA openssl-$OPENSSL_VER.tar.gz" | sha256sum -c -
        else
          notice "Downloaded: $(sha256sum "openssl-$OPENSSL_VER.tar.gz")"
        fi
        tar -xzf openssl-$OPENSSL_VER.tar.gz
        ln -s openssl-${OPENSSL_VER} openssl
      fi
    fi

    if [ "$BORINGSSL_VER" != 0 -a "$SSL_PROVIDER" = "boringssl" ]; then
      # Boring SSL

      if [[ ! -d "$BORINGSSL_DOWNLOAD" ]]; then
        warn "BoringSSL source not found, downloading..."
        set +e
        with_backoff curl --fail -sSL https://github.com/google/boringssl/archive/${BORINGSSL_VER}.zip -o boringssl-${BORINGSSL_VER}.zip
        [[ $? != 0 ]] && err "Could not download BoringSSL"
        set -e

        if [ ! -z ${BORINGSSL_SHA+x} ]; then
          echo "$BORINGSSL_SHA boringssl-${BORINGSSL_VER}.zip" | sha256sum -c -
        else
          notice "Downloaded: $(sha256sum "boringssl-${BORINGSSL_VER}.zip")"
        fi
        unzip -q boringssl-${BORINGSSL_VER}.zip
        ln -s boringssl-${BORINGSSL_VER} boringssl
      fi
    fi

    # OpenResty

    if [ ! -f $OPENRESTY_INSTALL/nginx/sbin/nginx ]; then
      if [[ $OPENRESTY_PATCHES == 0 && -f $OPENRESTY_DOWNLOAD/bundle/.patch_applied ]]; then
        warn "Patched OpenResty found but vanilla requested, removing source..."
        rm -rf $OPENRESTY_DOWNLOAD

      elif [[ $OPENRESTY_PATCHES != 0 && ! -f $OPENRESTY_DOWNLOAD/bundle/.patch_applied ]]; then
        warn "Vanilla OpenResty found but patches requested, removing Makefile..."
        rm -f $OPENRESTY_DOWNLOAD/Makefile
      fi

      if [ ! -d $OPENRESTY_DOWNLOAD ]; then
        warn "OpenResty source not found, downloading..."
        with_backoff curl -sSLO https://openresty.org/download/openresty-$OPENRESTY_VER.tar.gz
        if [ ! -z ${OPENRESTY_SHA+x} ]; then
          echo "$OPENRESTY_SHA openresty-$OPENRESTY_VER.tar.gz" | sha256sum -c -
        else
          notice "Downloaded: $(sha256sum "openresty-$OPENRESTY_VER.tar.gz")"
        fi
        tar -xzf openresty-$OPENRESTY_VER.tar.gz
        ln -s openresty-$OPENRESTY_VER openresty

        # use unreleased version of lua-resty-dns
        if version_eq $OPENRESTY_VER 1.19.3; then
          pushd openresty-$OPENRESTY_VER/bundle
            notice "Updating lua-resty-dns to unreleased version"
            with_backoff curl -sSL https://github.com/openresty/lua-resty-dns/tarball/ad4a51c8cae8c3fb8f712fa91fda660ab8a89669 -o lua-resty-dns-0.21.tar.gz
            tar -xzf lua-resty-dns-0.21.tar.gz --keep-newer-files -C lua-resty-dns-0.21 --strip-components 1
            rm -f lua-resty-dns-0.21.tar.gz
          popd
        fi

        if [[ $RESTY_WEBSOCKET != 0 ]]; then
          pushd openresty-$OPENRESTY_VER/bundle
            if ! rm -rf lua-resty-websocket-*; then
              fatal "Failed to find/remove upstream lua-resty-websocket component"
            fi
            mkdir "lua-resty-websocket-${RESTY_WEBSOCKET}"
            notice "Downloading Kong/lua-resty-websocket $RESTY_WEBSOCKET"
            with_backoff curl -sSL \
              -o lua-resty-websocket.tar.gz \
              "https://github.com/Kong/lua-resty-websocket/archive/refs/tags/${RESTY_WEBSOCKET}.tar.gz"
            tar -xzf lua-resty-websocket.tar.gz \
              -C "lua-resty-websocket-${RESTY_WEBSOCKET}" \
              --strip-components 1
            rm -f lua-resty-websocket.tar.gz
          popd
        fi
      fi
    fi

    # PCRE

    if [ ! -z "$PCRE_VER" ]; then
      PCRE_DOWNLOAD=$DOWNLOAD_CACHE/pcre-$PCRE_VER
      if [ ! -d $PCRE_DOWNLOAD ]; then
        warn "PCRE source not found, downloading..."
        with_backoff curl -sSLO https://downloads.sourceforge.net/project/pcre/pcre/${PCRE_VER}/pcre-${PCRE_VER}.tar.gz
        if [ ! -z ${PCRE_SHA+x} ]; then
          echo "$PCRE_SHA pcre-${PCRE_VER}.tar.gz" | sha256sum -c -
        else
          notice "Downloaded: $(sha256sum "pcre-${PCRE_VER}.tar.gz")"
        fi
        tar -xzf pcre-${PCRE_VER}.tar.gz
        ln -s pcre-${PCRE_VER} pcre
      fi
    fi

    # LuaRocks

    if [ ! -z "$LUAROCKS_VER" ]; then
      LUAROCKS_DESTDIR=${LUAROCKS_DESTDIR:-/}
      LUAROCKS_PREFIX=${LUAROCKS_PREFIX:-$PREFIX/luarocks}
      LUAROCKS_INSTALL=$(canon_path $LUAROCKS_DESTDIR/$LUAROCKS_PREFIX)
      if [ ! -f $LUAROCKS_INSTALL/bin/luarocks ]; then
        LUAROCKS_DOWNLOAD=$DOWNLOAD_CACHE/luarocks-$LUAROCKS_VER
        if [ ! -d $LUAROCKS_DOWNLOAD ]; then
          warn "LuaRocks source not found, downloading..."
          set +e
          with_backoff curl --fail -sSLO https://luarocks.org/releases/luarocks-$LUAROCKS_VER.tar.gz
          if [ $? != 0 ]; then
            with_backoff curl --fail -sSL https://github.com/luarocks/luarocks/archive/refs/tags/v$LUAROCKS_VER.tar.gz -o luarocks-$LUAROCKS_VER.tar.gz
            [ $? != 0 ] && err "Could not download luarocks source"
          fi
          set -e
          if [ ! -z ${LUAROCKS_SHA+x} ]; then
            echo "$LUAROCKS_SHA luarocks-$LUAROCKS_VER.tar.gz" | sha256sum -c -
          else
            notice "Downloaded: $(sha256sum "luarocks-$LUAROCKS_VER.tar.gz")"
          fi
          tar -xzf luarocks-$LUAROCKS_VER.tar.gz
          ln -s luarocks-${LUAROCKS_VER} luarocks
        fi
      fi
    fi

    # lua-kong-nginx-module
    if [ $KONG_NGINX_MODULE != 0 ]; then
      if [[ $KONG_NGINX_MODULE == "master" ]]; then
        KONG_NGINX_MODULE="origin/master"
      fi
      pushd $DOWNLOAD_CACHE
        if [ ! -d lua-kong-nginx-module ]; then
          warn "lua-kong-nginx-module source not found, cloning..."
          git clone https://github.com/Kong/lua-kong-nginx-module
        fi

        pushd lua-kong-nginx-module
          git fetch
          # Accept both tags, branches and SHAs
          git reset --hard $KONG_NGINX_MODULE || git reset --hard origin/$KONG_NGINX_MODULE
        popd
      popd
    fi

    # lua-resty-lmdb
    if [ $RESTY_LMDB != 0 ]; then
      if [[ $RESTY_LMDB == "master" ]]; then
        RESTY_LMDB="origin/master"
      fi
      pushd $DOWNLOAD_CACHE
        if [ ! -d lua-resty-lmdb ]; then
          warn "lua-resty-lmdb source not found, cloning..."
          git clone https://github.com/Kong/lua-resty-lmdb --recursive
        fi

        pushd lua-resty-lmdb
          git fetch
          # Accept both tags, branches and SHAs
          git reset --hard $RESTY_LMDB || git reset --hard origin/$RESTY_LMDB
        popd
      popd
    fi

    # lua-resty-events
    if [ $RESTY_EVENTS != 0 ]; then
      if [[ $RESTY_EVENTS == "main" ]]; then
        RESTY_EVENTS="origin/main"
      fi
      pushd $DOWNLOAD_CACHE
        if [ ! -d lua-resty-events ]; then
          warn "lua-resty-events source not found, cloning..."
          git clone https://github.com/Kong/lua-resty-events --recursive
        fi

        pushd lua-resty-events
          git fetch
          # Accept both tags, branches and SHAs
          git reset --hard $RESTY_EVENTS || git reset --hard origin/$RESTY_EVENTS
        popd
      popd
    fi

    # atc-router
    if [ $ATC_ROUTER != 0 ]; then
      if [[ $ATC_ROUTER == "main" ]]; then
        ATC_ROUTER="origin/main"
      fi
      pushd $DOWNLOAD_CACHE
        if [ ! -d atc-router ]; then
          warn "atc-router source not found, cloning..."
          git clone https://github.com/Kong/atc-router --recursive
        fi

        pushd atc-router
          git fetch
          # Accept both tags, branches and SHAs
          git reset --hard $ATC_ROUTER || git reset --hard origin/$ATC_ROUTER
        popd
      popd
    fi
  popd

  if [ ! -z "$DOWNLOAD_ONLY" ]; then
    notice "Exiting early since --download-extract-only was specified"
    exit 0
  fi

  notice "Patching the components now..."

  if [ ! -f $OPENRESTY_INSTALL/nginx/sbin/nginx ]; then
    notice "Patching OpenResty..."

    if [[ $OPENRESTY_PATCHES != 0 ]]; then

      if [[ ! -d "$OPENRESTY_PATCHES_DIR" ]]; then
        fatal "directory does not exist: $OPENRESTY_PATCHES_DIR"
      fi

      if [[ ! -d "$OPENRESTY_PATCHES_DIR/patches/$OPENRESTY_VER" ]]; then
        fatal "no patches for OpenResty $OPENRESTY_VER (missing directory $OPENRESTY_PATCHES_DIR/patches/$OPENRESTY_VER)"
      fi

      pushd $OPENRESTY_DOWNLOAD/bundle
        if [ ! -f .patch_applied ]; then
          for patch_file in $(ls -1 $OPENRESTY_PATCHES_DIR/patches/$OPENRESTY_VER/*.patch); do
            # any existing patch files will surely conflict with our fork of
            # lua-resty-websocket, so skip them if bundling Kong/lua-resty-websocket
            if
              [[ $RESTY_WEBSOCKET != 0 ]] &&
              [[ "$(basename "$patch_file")" = lua-resty-websocket-* ]]
            then
              notice "Skipping lua-resty-websocket patch: $patch_file"
              continue
            fi

            notice "Applying OpenResty patch $patch_file"
            patch -p1 < $patch_file \
              || fatal "failed to apply patch: $patch_file"
          done

          touch .patch_applied
        fi
      popd

      if [ ! -f $OPENRESTY_DOWNLOAD/bundle/.patch_applied ]; then
        fatal "missing .patch_applied file; some OpenResty patches may not have been applied"
      fi
    fi

    # apply non Kong-specific patches

    if version_lt $NGINX_CORE_VER 1.15.0; then # this is fixed in Nginx 1.15.0
      if [[ $DIST == "Fedora" && $DIST_VER -gt 28 ]]; then
        warn "Fedora 28 or above detected, applying the 'rm_glibc_crypt_r_workaround' patch..."
        pushd $OPENRESTY_DOWNLOAD/bundle/nginx-$NGINX_CORE_VER
          patch --forward -p1 < $SCRIPT_PATH/patches/nginx-$NGINX_CORE_VER-rm_glibc_crypt_r_workaround.patch || true
        popd
      fi
    fi

    if version_eq $OPENRESTY_VER 1.15.8; then # this occurs in OpenResty 1.15.8.x
      if [ ! -z "$PCRE_VER" ]; then
        warn "Building OpenResty $OPENRESTY_VER with static libpcre, applying the fix_static_libpcre_linking patch..."
        pushd $OPENRESTY_DOWNLOAD/bundle/ngx_lua-0.10.15
          patch --forward -p1 < $SCRIPT_PATH/patches/openresty-1.15.8.x-fix_static_libpcre_linking.patch || true
        popd
      fi
    fi

    # CVEs - http://nginx.org/en/security_advisories.html

    if version_lt $NGINX_CORE_VER 1.15.6; then # fixed in NGINX 1.15.6+
      #if version_lt $OPENRESTY_VER 1.13.6.3; then # also included in OpenResty 1.13.6.3+ (not yet released)
        warn "Applying the patch for CVE-2018-16843 CVE-2018-16844..."
        pushd $OPENRESTY_DOWNLOAD/bundle/nginx-$NGINX_CORE_VER
          if [ ! -f $SCRIPT_PATH/patches/nginx-$NGINX_CORE_VER-cve_2018_16843_cve_2018_16844.patch ]; then
            fatal "Missing patch nginx-$NGINX_CORE_VER-cve_2018_16843_cve_2018_16844.patch"
          fi
          patch --forward -p1 < $SCRIPT_PATH/patches/nginx-$NGINX_CORE_VER-cve_2018_16843_cve_2018_16844.patch || true
        popd

        warn "Applying the patch for CVE-2018-16845..."
        pushd $OPENRESTY_DOWNLOAD/bundle/nginx-$NGINX_CORE_VER
          if [ ! -f $SCRIPT_PATH/patches/nginx-patch.2018.mp4.txt ]; then
            fatal "Missing patch nginx-patch.2018.mp4.txt"
          fi
          patch --forward -p0 < $SCRIPT_PATH/patches/nginx-patch.2018.mp4.txt || true
        popd
      #fi
    fi

    if version_lt $NGINX_CORE_VER 1.17.3 \
       && version_lt $OPENRESTY_VER 1.15.8.2; then
        warn "Applying the patch for CVE-2019-9511 CVE-2019-9513 CVE-2019-9516..."
        pushd $OPENRESTY_DOWNLOAD/bundle/nginx-$NGINX_CORE_VER
          if [ ! -f $SCRIPT_PATH/patches/nginx-$NGINX_CORE_VER-cve_2019_9511_cve_2019_9513_cve_2019_9516.patch ]; then
            fatal "Missing patch nginx-$NGINX_CORE_VER-cve_2019_9511_cve_2019_9513_cve_2019_9516.patch"
          fi
          patch --forward -p1 < $SCRIPT_PATH/patches/nginx-$NGINX_CORE_VER-cve_2019_9511_cve_2019_9513_cve_2019_9516.patch || true
        popd
      #fi
    fi
  fi

  notice "Building the components now..."

  # Building OpenSSL

  if [ "$OPENSSL_VER" != 0 -a "$SSL_PROVIDER" = "openssl" ]; then
    if [ ! -f $OPENSSL_INSTALL/bin/openssl ]; then
      notice "Building OpenSSL..."

      pushd $OPENSSL_DOWNLOAD
        if (version_lte $OPENSSL_VER 1.0 && [[ ! -d include/openssl ]]) || [[ ! -f Makefile ]]; then
            OPENSSL_OPTS=(
              "-g"
              "shared"
              "-DPURIFY"
              "no-threads"
              "--prefix=$OPENSSL_PREFIX"
              "--openssldir=$OPENSSL_PREFIX"
            )

            if version_gte $OPENSSL_VER 1.1.0; then
              OPENSSL_OPTS+=('no-unit-test')

            else
              OPENSSL_OPTS+=('no-tests')
            fi

            if ([[ $CC == "clang" ]] && version_gte $OPENSSL_VER 1.1) || [[ $CC != "clang" ]]; then
              local ld_opts="-Wl,-rpath,'\$(LIBRPATH)'"
              if [[ $OS != "Darwin" ]]; then
                ld_opts="$ld_opts,--enable-new-dtags"
              fi

              OPENSSL_OPTS+=("$ld_opts")
            fi

            if [ $DEBUG == 1 ]; then
              OPENSSL_OPTS+=('-d')
            fi

          eval ./config ${OPENSSL_OPTS[*]}
        fi

        if version_gte $OPENSSL_VER 1.1.0; then
          make -j$NPROC
        else
          make
        fi

        make install_sw DESTDIR=${OPENSSL_DESTDIR}
      popd

      succ "OpenSSL $OPENSSL_VER has been built successfully!"

    else
      succ "OpenSSL $OPENSSL_VER has been built successfully (cached)!"
    fi
  fi

  if [ "$BORINGSSL_VER" != 0 -a "$SSL_PROVIDER" = "boringssl" ]; then
    if [ ! -d "$BORINGSSL_DOWNLOAD/.openssl/lib" ]; then
      notice "Building BoringSSL..."

      arch=$(uname -m)
      if [ "$(arch)" != "x86_64" ]; then
        echo "Error: BoringSSL builds only supported in x86_64; got $arch" >&2
        exit 1
      fi

      pushd $BORINGSSL_DOWNLOAD

        # BoringCrypto Security Policy
        # (https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf)
        # Establishes the requirement of specific versions of the toolchain,
        # comprised of:
        #   clang compiler v7.0.1
        #   golang v1.12.7
        #   ninja build system v1.9.0
        #
        # XXX DO NOT change lines below unless otherwise mandates by newer
        # revisions of the Security Policy

        clang_version=7.0.1
        golang_version=1.12.7
        ninja_version=1.9.0

        OLD_PATH="$PATH"
        OLD_HOME="$HOME"
        export HOME="$PWD"

        notice "BoringSSL: setting up local toolchain..."

        # clang
          if [[ $DIST == "CentOS Linux" ]]; then
            llvm_url="https://releases.llvm.org/$clang_version/clang+llvm-$clang_version-x86_64-linux-gnu-ubuntu-14.04.tar.xz"
            llvm_checksum="e1544abbbe4a4e306061f5076dcef504c15c1ca342730895ce2c194ed78e647a"
            boringssl_cmake=cmake3
          else # we only support ubuntu 20.04 other than rhel now
            llvm_url="https://releases.llvm.org/$clang_version/clang+llvm-$clang_version-x86_64-linux-gnu-ubuntu-16.04.tar.xz"
            llvm_checksum="02ad925add5b2b934d64c3dd5cbd1b2002258059f7d962993ba7f16524c3089c"
            boringssl_cmake=cmake
          fi
          mkdir llvm
          with_backoff curl -sSL "$llvm_url" -o llvm.tar.xz
          echo "$llvm_checksum llvm.tar.xz" | sha256sum -c

          tar -C llvm --strip-components 1 -Jxf llvm.tar.xz
          export PATH="$PWD/llvm/bin:$PATH"
          printf "set(CMAKE_C_COMPILER \"clang\")\nset(CMAKE_CXX_COMPILER \"clang++\")\n" > ${HOME}/toolchain
          notice "BoringSSL: using clang $(clang --version)"
        #

        # golang
          golang_url="https://go.dev/dl/go${golang_version}.linux-amd64.tar.gz"
          golang_checksum="66d83bfb5a9ede000e33c6579a91a29e6b101829ad41fffb5c5bb6c900e109d9"
          with_backoff curl -sSL "$golang_url" -o golang.tar.gz
          echo "$golang_checksum golang.tar.gz" | sha256sum -c
          tar -zxf golang.tar.gz

          export GOPATH="$PWD/gopath"
          export GOROOT="$PWD/go"
          export PATH="$GOPATH/bin:$GOROOT/bin:$PATH"
          notice "BoringSSL: using golang $(go version)"
        #

        # ninja
          ninja_url="https://github.com/ninja-build/ninja/archive/refs/tags/v$ninja_version.zip"
          ninja_checksum="8e2e654a418373f10c22e4cc9bdbe9baeca8527ace8d572e0b421e9d9b85b7ef"
          with_backoff curl -sSL "$ninja_url" -o ninja.zip
          echo "$ninja_checksum ninja.zip" | sha256sum -c
          unzip -o ninja.zip
          pushd ninja-$ninja_version
            ln -s /usr/bin/python3 /usr/local/bin/python
            export PATH="$PATH:/usr/local/bin"
            ./configure.py --bootstrap
            export PATH="$PWD:$PATH"
          popd
          notice "BoringSSL: using ninja $(ninja --version)"
        #

        # build boringssl in fips mode

        mkdir -p build
        pushd build
          $boringssl_cmake -GNinja \
                -DCMAKE_TOOLCHAIN_FILE=${HOME}/toolchain \
                -DFIPS=1 \
                -DCMAKE_BUILD_TYPE=Release \
                -DBUILD_SHARED_LIBS=1 ..
          unset $boringssl_cmake
          ninja
        popd #build

        PATH="$OLD_PATH"
        HOME="$OLD_HOME"
        unset GOPATH GOROOT

        mkdir -p .openssl/lib
        cp -v build/crypto/libcrypto.* build/ssl/libssl.* .openssl/lib
        pushd .openssl
          ln -s ../include .
        popd #.openssl

        cp -vp build/crypto/libcrypto.* build/ssl/libssl.* $OPENSSL_DESTDIR$OPENSSL_PREFIX/lib
        cp -rvp include/openssl $OPENSSL_DESTDIR$OPENSSL_PREFIX/include/
      popd # $OPENRESTY_DOWNLOAD
      succ "BoringSSL $BORINGSSL_VER has been built successfully!"

    else
      succ "BoringSSL $BORINGSSL_VER has been built successfully (cached)!"
    fi
  fi

  # Building OpenResty

  if [ ! -f $OPENRESTY_INSTALL/nginx/sbin/nginx ]; then
    notice "Building OpenResty..."

    pushd $OPENRESTY_DOWNLOAD
      if [ ! -f Makefile ]; then

        pushd bundle
          lj_dir=$(ls -d LuaJIT*)
          lj_release_date=$(echo ${lj_dir} | sed -e 's/LuaJIT-[[:digit:]]\+.[[:digit:]]\+-\([[:digit:]]\+\)/\1/')
          lj_version_tag="LuaJIT\ 2.1.0-${lj_release_date}"
        popd

        notice "Building LuaJIT with version string $lj_version_tag"

        OPENRESTY_OPTS=(
          "--prefix=$OPENRESTY_PREFIX"
          "--with-pcre-jit"
          "--with-http_ssl_module"
          "--with-http_sub_module"
          "--with-http_realip_module"
          "--with-http_stub_status_module"
          "--with-http_v2_module"
          "--without-http_encrypted_session_module"
          "--with-luajit-xcflags='-DLUAJIT_VERSION=\\\"${lj_version_tag}\\\"'"
          "-j$NPROC"
        )

        if [ "$EDITION" == 'enterprise' ]; then
          if [ "$ENABLE_KONG_LICENSING" != "false" ]; then
            OPENRESTY_OPTS+=("--add-module=$KONG_DISTRIBUTION_PATH/kong-licensing/ngx_module")
          fi
          OPENRESTY_OPTS+=("--add-module=$KONG_DISTRIBUTION_PATH/lua-resty-openssl-aux-module")
        fi

        if [ $KONG_NGINX_MODULE != 0 ]; then
          OPENRESTY_OPTS+=("--add-module=$DOWNLOAD_CACHE/lua-kong-nginx-module")
          if [[ -d $DOWNLOAD_CACHE/lua-kong-nginx-module/stream ]]; then
            OPENRESTY_OPTS+=("--add-module=$DOWNLOAD_CACHE/lua-kong-nginx-module/stream")
          fi
        fi

        if [ $RESTY_LMDB != 0 ]; then
          OPENRESTY_OPTS+=("--add-module=$DOWNLOAD_CACHE/lua-resty-lmdb")
        fi

        if [ $RESTY_EVENTS != 0 ]; then
          OPENRESTY_OPTS+=("--add-module=$DOWNLOAD_CACHE/lua-resty-events")
        fi

        if version_gte $NGINX_CORE_VER 1.11.4; then
          OPENRESTY_OPTS+=('--with-stream_realip_module')
        fi

        if version_gte $NGINX_CORE_VER 1.11.5; then
          OPENRESTY_OPTS+=('--with-stream_ssl_preread_module')
        fi

        if [ ! -z "$PCRE_VER" ]; then
          OPENRESTY_OPTS+=("--with-pcre=$PCRE_DOWNLOAD")

        else
          OPENRESTY_OPTS+=('--with-pcre')
        fi

        OPENRESTY_OPTS+=(${NGINX_EXTRA_MODULES[@]})

        OPENRESTY_RPATH=${OPENRESTY_RPATH:-$OPENSSL_INSTALL/lib}

        if [ "$BORINGSSL_VER" != 0 -a "$SSL_PROVIDER" = "boringssl" ]; then

          OPENRESTY_OPTS+=(
            "--with-cc-opt='-I$BORINGSSL_DOWNLOAD/.openssl/include'"
            "--with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -L$BORINGSSL_DOWNLOAD/.openssl/lib -Wl,--disable-new-dtags,-rpath,$OPENRESTY_RPATH'"
          )

        else
          if ld --disable-new-dtags 2>&1 >/dev/null | grep -q "disable-new-dtags"; then
            OPENRESTY_OPTS+=("--with-ld-opt='-L$OPENSSL_INSTALL/lib -Wl,-rpath,$OPENRESTY_RPATH'")
          else
            OPENRESTY_OPTS+=("--with-ld-opt='-L$OPENSSL_INSTALL/lib -Wl,--disable-new-dtags,-rpath,$OPENRESTY_RPATH'")
          fi

          OPENRESTY_OPTS+=("--with-cc-opt='-I$OPENSSL_INSTALL/include'")
        fi

        if [ $DEBUG == 1 ]; then
          OPENRESTY_OPTS+=('--with-debug')
          OPENRESTY_OPTS+=('--with-no-pool-patch')
          OPENRESTY_OPTS+=('--with-luajit-xcflags="-DLUAJIT_USE_VALGRIND -DLUA_USE_ASSERT -DLUA_USE_APICHECK -DLUAJIT_USE_SYSMALLOC"')
        fi

        eval ./configure ${OPENRESTY_OPTS[*]}

        if [ "$BORINGSSL_VER" != 0 -a "$SSL_PROVIDER" = "boringssl" ]; then
          touch $BORINGSSL_DOWNLOAD/.openssl/include/openssl/ssl.h
        fi
      fi

      make -j$NPROC
      make -j$NPROC install DESTDIR=${OPENRESTY_DESTDIR}

      if [ $KONG_NGINX_MODULE != 0 ]; then
        pushd $DOWNLOAD_CACHE/lua-kong-nginx-module
          warn "installing library files for lua-kong-nginx-module..."
          make install LUA_LIB_DIR=$OPENRESTY_INSTALL/lualib
        popd
      fi

      if [ $RESTY_LMDB != 0 ]; then
        pushd $DOWNLOAD_CACHE/lua-resty-lmdb
          warn "installing library files for lua-resty-lmdb..."
          make install LUA_LIB_DIR=$OPENRESTY_INSTALL/lualib
        popd
      fi

      if [ $RESTY_EVENTS != 0 ]; then
        pushd $DOWNLOAD_CACHE/lua-resty-events
          warn "installing library files for lua-resty-events..."
          make install LUA_LIB_DIR=$OPENRESTY_INSTALL/lualib
        popd
      fi

      if [ $ATC_ROUTER != 0 ]; then
        local atc_router_install_succeed=0
        pushd $DOWNLOAD_CACHE/atc-router
          warn "installing dynamic library and Lua files for atc-router..."

          # the env script adds the cargo bin dir to PATH if missing
          which cargo > /dev/null || source "${CARGO_HOME:-$HOME/.cargo}"/env

          # the atc-router makefile also tests for availability of cargo
          make install LUA_LIB_DIR=$OPENRESTY_INSTALL/lualib || atc_router_install_succeed=1

          if [ $atc_router_install_succeed -eq 1 ]; then
            warn "error while installing atc-router... retrying with RUSTFLAGS='-C target-feature=-crt-static'..."
            export RUSTFLAGS="-C target-feature=-crt-static"
            make clean install LUA_LIB_DIR=$OPENRESTY_INSTALL/lualib
          fi
        popd
      fi
    popd

    succ "OpenResty $OPENRESTY_VER has been built successfully!"

  else
    succ "OpenResty $OPENRESTY_VER has been built successfully (cached)!"
  fi

  # Building LuaRocks

  if [ ! -z "$LUAROCKS_VER" ]; then
    if [ ! -f $LUAROCKS_INSTALL/bin/luarocks ]; then
      notice "Building LuaRocks..."

      pushd $LUAROCKS_DOWNLOAD
        if [ ! -f config.unix ]; then
          ./configure \
            --prefix=$LUAROCKS_PREFIX \
            --lua-suffix=jit \
            --with-lua=$OPENRESTY_INSTALL/luajit \
            --with-lua-include=$OPENRESTY_INSTALL/luajit/include/luajit-2.1
        fi

        make build -j$NPROC
        make install DESTDIR=${LUAROCKS_DESTDIR}
      popd

      succ "LuaRocks $LUAROCKS_VER has been built successfully!"

    else
      succ "LuaRocks $LUAROCKS_VER has been built successfully (cached)!"
    fi
  fi

  succ "Build finished in $SECONDS seconds. Enjoy!"
}

parse_version() {
  [[ -z $1 ]] && fatal 'missing arg $1 when invoking parse_version()'
  [[ -z $2 ]] && fatal 'missing arg $2 when invoking parse_version()'

  local ver
  local subj=$1

  if [[ $subj =~ ^[^0-9]*(.*) ]]; then
    subj=${BASH_REMATCH[1]}

    local re='^(-rc[0-9]+$)?[.]?([0-9]+|[a-zA-Z]+)?(.*)$'

    while [[ $subj =~ $re ]]; do
      if [[ ${BASH_REMATCH[1]} != "" ]]; then
        ver="$ver.${BASH_REMATCH[1]}"
      fi

      if [[ ${BASH_REMATCH[2]} != "" ]]; then
        ver="$ver.${BASH_REMATCH[2]}"
      fi

      subj="${BASH_REMATCH[3]}"
      if [[ $subj == "" ]]; then
        break
      fi
    done

    ver="${ver:1}"

    IFS='.' read -r -a $2 <<< "$ver"
  fi
}

parse_nginx_core_version() {
  [[ -z $1 ]] && fatal 'missing arg $1 when invoking parse_nginx_core_version()'

  local nginx_ver

  parse_version $1 nginx_ver

  echo "${nginx_ver[0]}.${nginx_ver[1]}.${nginx_ver[2]}"
}

version_eq() {
  local version_a version_b

  parse_version $1 version_a
  parse_version $2 version_b

  # Note that we are indexing on the b components, ie: 1.11.100 == 1.11
  for index in "${!version_b[@]}"; do
    [[ "${version_a[index]}" != "${version_b[index]}" ]] && return 1
  done

  return 0
}

version_lt() {
  local version_a version_b

  parse_version $1 version_a
  parse_version $2 version_b

  for index in "${!version_a[@]}"; do
    if [[ ${version_a[index]} =~ ^[0-9]+$ ]]; then
      [[ "${version_a[index]}" -lt "${version_b[index]}" ]] && return 0
      [[ "${version_a[index]}" -gt "${version_b[index]}" ]] && return 1

    else
      [[ "${version_a[index]}" < "${version_b[index]}" ]] && return 0
      [[ "${version_a[index]}" > "${version_b[index]}" ]] && return 1
    fi
  done

  return 1
}

version_gt() {
  (version_eq $1 $2 || version_lt $1 $2) && return 1
  return 0
}

version_lte() {
  (version_lt $1 $2 || version_eq $1 $2) && return 0
  return 1
}

version_gte() {
  (version_gt $1 $2 || version_eq $1 $2) && return 0
  return 1
}

canon_path() {
  if realpath -m -- $1 2>/dev/null >&2; then
    realpath -m -- $1

  else
    readlink -f -- $1
  fi
}

n_proc() {
  if nproc 2>/dev/null >&2;  then
    nproc

  elif [[ $OS == "Darwin" ]]; then
    sysctl -n hw.physicalcpu

  else
    echo "1"
  fi
}

show_usage() {
  echo "Build basic components (OpenResty, OpenSSL and LuaRocks) for Kong."
  echo ""
  echo "Usage: $0 [options...] -p <prefix> --openresty <openresty_ver> --openssl <openssl_ver>"
  echo ""
  echo "Required arguments:"
  echo "  -p, --prefix <prefix>              Location where components should be installed."
  echo "      --openresty <openresty_ver>    Version of OpenResty to build, such as 1.13.6.2."
  echo "      --openssl <openssl_ver>        Version of OpenSSL to build, such as 1.1.1c."
  echo ""
  echo "      --boringssl <boringssl_ver>    Version of BoringSSL to build. If absent, BoringSSL will"
  echo "                                     not be build."
  echo ""
  echo "      --ssl-provider                 Specify a provider for SSL libraries"
  echo "                                     (Can be set to \"openssl\" or \"boringssl\")"
  echo ""
  echo "Optional arguments:"
  echo "      --no-openresty-patches         Do not apply openresty-patches while compiling OpenResty."
  echo "                                     (Patching is enabled by default)"
  echo ""
  echo "      --no-kong-nginx-module         Do not include lua-kong-nginx-module while patching and compiling OpenResty."
  echo "                                     (Patching and compiling is enabled by default for OpenResty > 1.13.6.1)"
  echo ""
  echo "      --kong-nginx-module <branch>   Specify a lua-kong-nginx-module branch to use when patching and compiling."
  echo "                                     (Defaults to \"master\")"
  echo "      --no-resty-lmdb                Do not include lua-resty-lmdb while patching and compiling OpenResty."
  echo ""
  echo "      --resty-lmdb <branch>          Specify a lua-resty-lmdb branch to use when patching and compiling."
  echo "                                     (Defaults to \"master\")"
  echo ""
  echo "      --resty-websocket <tag>        Specify a Kong/lua-resty-websocket tag to install. If absent, lua-resty-websocket"
  echo "                                     will not be built."
  echo ""
  echo "      --atc-router <tag>             Specify a Kong/atc-router tag to install. If absent, atc-router"
  echo "                                     will not be built."
  echo ""
  echo "      --luarocks <luarocks_ver>      Version of LuaRocks to build, such as 3.1.2. If absent, LuaRocks"
  echo "                                     will not be built."
  echo ""
  echo "      --pcre <pcre_ver>              Version of PCRE to build, such as 8.43. If absent, PCRE will"
  echo "                                     not be build."
  echo ""
  echo "      --add-module <module_path>     Path to additional NGINX module to be built. This option can be"
  echo "                                     repeated and will be passed to NGINX's configure in the order"
  echo "                                     they were specified."
  echo ""
  echo "      --debug                        Disable compile-time optimizations and memory pooling for NGINX,"
  echo "                                     LuaJIT and OpenSSL to help debugging."
  echo ""
  echo "  -j, --jobs                         Concurrency level to use when building."
  echo "                                     (Defaults to number of CPU cores available: $NPROC)"
  echo ""
  echo "      --work <work>                  The working directory to use while compiling."
  echo "                                     (Defaults to \"work\")"
  echo ""
  echo "  -f, --force                        Build from scratch."
  echo -e "                                     \033[1;31mWARNING:\033[0m This permanently removes everything inside the <work> and <prefix> directories."
  echo ""
  echo "  -h, --help                         Show this message."
}

notice() {
  builtin echo -en "\033[1m"
  echo "NOTICE: $@"
  builtin echo -en "\033[0m"
}

succ() {
  builtin echo -en "\033[1;32m"
  echo "SUCCESS: $@"
  builtin echo -en "\033[0m"
}

warn() {
  builtin echo -en "\033[1;33m"
  echo "WARN: $@"
  builtin echo -en "\033[0m"
}

fatal() {
  builtin echo -en "\033[1;31m"
  echo "FATAL: $@"
  builtin echo -en "\033[0m"
  exit 1
}

err() {
  builtin echo -en "\033[1;31m"
  echo "ERR: $@"
  builtin echo -en "\033[0m"
  exit 1
}

if [[ $(basename $(canon_path $0)) == "kong-ngx-build" ]]; then
  main $@
fi

# vi: ts=2 sts=2 sw=2 et
